package zunge.lbe.analysis;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LibLinear {
	public String folder;
	public String filePrefix;
	public static final String fsuf_train = "_train.txt";
	public static final String fsuf_test = "_test.txt";
	public static final String fsuf_model = "_model.txt";
	public static final String fsuf_out = "_out.txt";
	public static final String fsuf_log = "_log.txt";
	
	public static final String fsuf_prob_model = "_prob_model.txt";
	public static final String fsuf_prob_out = "_prob_out.txt";
	public static final String fsuf_prob_log = "_prob_log.txt";
	
	public String getFile(String label){
		return folder+filePrefix+label;
	}
	
	
	public LibLinear(String folder, String filePrefix){
		this.folder = folder;
		this.filePrefix = filePrefix;
	}
	
	
	public static final int FLAG_NUMERIC = 0;
	public static final int FLAG_NOMINAL = 1;
	/** Input: csv files: target value is at the last field
	 *  Output: shell script to run the liblinear
	 */
	public void genData(String fin_train, String fin_test, int[] flags, boolean[] valids) throws Exception{
		//Read Delta
		int[] featureSize = new int[flags.length];
		BufferedReader br = new BufferedReader(new FileReader(fin_train));
		String line;
		while((line=br.readLine())!=null){
			String[] frags = line.split(" ");
			for(int i = 0;i < frags.length;++i){
				if(valids[i]==false){featureSize[i] = 0;}
				else{
					if(flags[i]==FLAG_NOMINAL){ featureSize[i] = Math.max(Integer.parseInt(frags[i]), featureSize[i]);}
					else{ featureSize[i] = 1;}
				}
			}
		}
		br.close();
		int[] delta = new int[flags.length];
		delta[0] = 0; for(int i = 1;i < flags.length;++i){delta[i] = delta[i-1] + featureSize[i-1];}
		
		//Generate train
		br = new BufferedReader(new FileReader(fin_train));
		BufferedWriter bw = new BufferedWriter(new FileWriter(getFile(fsuf_train)));
		while((line=br.readLine())!=null){
			String[] frags = line.split(" ");
			bw.write(frags[flags.length-1]);
			for(int i = 0;i < flags.length-1;++i){
				if(!valids[i]){continue;}
				else if(flags[i]==FLAG_NOMINAL){
					int featureId = Integer.parseInt(frags[i])+delta[i];
					bw.write(" "+featureId+":"+1);
				}else{
					int featureId = 1+delta[i];
					bw.write(" "+featureId+":"+frags[i]);
				}
			}
			bw.write("\n");
		}
		br.close();
		bw.close();
		
		//Generate test
		br = new BufferedReader(new FileReader(fin_test));
		bw = new BufferedWriter(new FileWriter(getFile(fsuf_test)));
		while((line=br.readLine())!=null){
			String[] frags = line.split(" ");
			bw.write(frags[flags.length-1]);
			for(int i = 0;i < flags.length-1;++i){
				if(!valids[i]){continue;}
				else if(flags[i]==FLAG_NOMINAL){
					int fId = Integer.parseInt(frags[i]);
					if(fId > featureSize[i]){continue;}//Test data point unknown
					int featureId = fId + delta[i];
					bw.write(" "+featureId+":"+1);
				}else{
					int featureId = 1+delta[i];
					bw.write(" "+featureId+":"+frags[i]);
				}
			}
			bw.write("\n");
		}
		br.close();
		bw.close();
		
		//Clear files
		new File(getFile(fsuf_model)).delete();
		new File(getFile(fsuf_out)).delete();
		new File(getFile(fsuf_log)).delete();
		
	}
	
	/**
	 * LibLinear: L2-loss support vector classification (dual)
	 */
	public String genScript(String libFolder){
		return (libFolder+"liblinear/train -s 1 -q ")
				+getFile(fsuf_train)+" "+getFile(fsuf_model)
				+" && "
				+(libFolder+"liblinear/predict ")+getFile(fsuf_test)+" "+getFile(fsuf_model)
				+" "+getFile(fsuf_out)+" > "+getFile(fsuf_log);
	}
	/** 
	 * LibLinear: L2-loss logistic regression with probability estimate for all testing data points
	 */
	public String genScriptProb(String libFolder){
		return (libFolder+"liblinear/train -s 7 -q ")
				+getFile(fsuf_train)+" "+getFile(fsuf_prob_model)
				+" && "
				+(libFolder+"liblinear/predict -b 1 ")+getFile(fsuf_test)+" "+getFile(fsuf_prob_model)
				+" "+getFile(fsuf_prob_out)+" > "+getFile(fsuf_prob_log);
	}

	/**
	 * accuracy in percentage
	 */
	public double getAccuracy() throws Exception{
		BufferedReader br = new BufferedReader(new FileReader(getFile(fsuf_log)));
		String line = br.readLine();
		double acc = Double.parseDouble(line.substring(line.indexOf('=')+1, line.indexOf('%')));
		br.close();
		return acc;
	}
	
	/**
	 * accuracy in percentage: probability estimates
	 */
	public static final int PROB_CURVE_SIZE = 1000;
	public List<ProbCurvePoint> getProbCurve() throws Exception{
		List<ProbRecord> records = new ArrayList<ProbRecord>();
		//Read output
		BufferedReader br = new BufferedReader(new FileReader(getFile(fsuf_prob_out)));
		String line = br.readLine();//skip title
		while((line=br.readLine())!=null){
			String[] frags = line.split(" ");
			ProbRecord pr = new ProbRecord();
			pr.prediction = Integer.parseInt(frags[0]);
			pr.confidence = Math.max(Double.parseDouble(frags[1]), Double.parseDouble(frags[2]));
			records.add(pr);
		}
		//Read labels from test dataset
		br.close();
		br = new BufferedReader(new FileReader(getFile(fsuf_test)));
		for(int i = 0;i < records.size();++i){
			line = br.readLine();
			records.get(i).label = Integer.parseInt(line.substring(0,line.indexOf(" ")));
		}
		br.close();
		
		//Check all choices of criteria, and calculate accuracy for each of them.
		List<ProbCurvePoint> pcps = new ArrayList<ProbCurvePoint>();
		for(int ps = 0;ps < PROB_CURVE_SIZE;++ps){
			double criteria = 0.5 + 0.5 * ps / (PROB_CURVE_SIZE); //confidence have to be greater or equal than criteria
			double accuracy = 0;
			double count = 0;
			for(ProbRecord pr:records){
				if(pr.confidence >= criteria){
					accuracy += (pr.label==pr.prediction)?1:0;
					count += 1;
				}
			}
			if(count > 0){accuracy /= count;}
			pcps.add(new ProbCurvePoint(ps, criteria, accuracy, count/records.size()));
		}
		return pcps;
	}
	
	public class ProbRecord implements Comparable<ProbRecord>{
		int label;
		int prediction;
		Double confidence;
		@Override
		public int compareTo(ProbRecord arg0) {
			return arg0.confidence.compareTo(confidence);
		}
	}
	public class ProbCurvePoint{
		int id;
		double criteria;
		double precision;
		double coverage;
		public ProbCurvePoint(int id, double c,double p, double cv){
			this.id = id;this.criteria = c;this.precision = p;this.coverage = cv;
		}
	}
}
